Sub

Which Linux distribution you use?













ECOM: plugins for Symbian OS

ECOM: plugins for Symbian OS

Rafał Kocisz

My aim in writing this article was to provide a clear and simple yet detailed introduction to ECOM, and to this end I\'ve put together a simple (though non-trivial) application demonstrating the mechanisms discussed. The sample program provides the basis for a step-by-step tutorial on creating an ECOM plugin. The source code and architecture of the application are carefully designed and thoroughly tested, so they can be used to build both training programs and production-quality applications.

Before we begin...

Developing software for the Symbian OS is a fairly complex and challenging task, especially for beginners. There are several reasons for this, starting with the necessity of working in an emulated development environment. An additional problem is the relative scarcity of good literature on Symbian development. Not that I\'m trying to discourage you. Far from it - in writing this article, I wanted to show that even such complex issues as plugin development can be mastered quite easily, of course provided you have the necessary background knowledge. The article is not intended as a starting point for Symbian programming, so if you\'re completely unfamiliar with the platform I suggest you catch up on the basics first. A good place to start would be the Symbian workshop in the May 2005 issue of SDJ, as it provides a friendly and concise introduction to the essentials of Symbian development.

The approach I\'ve adopted for this article is that information not directly related to ECOM, but useful for understanding the big picture, can be found in Frames throughout the article. Hopefully this will make the key concepts easier to understand for everyone less familiar with Symbian while keeping the workshop focussed. It\'s also worth having a look at the web resources listed towards the end of the article, and anyone intent on seriously getting into Symbian development would do well to get their hands on the books listed in the Frame Further reading. If you have any questions concerning the article, feel free to contact me.

Plugin framework

Most software users are intuitively familiar with the concept of plugins as dynamically added extensions to core application functionality. Many popular programs - most notably media players - support plugins that extend their basic capabilities (for example adding support for new file formats without reinstalling the whole application) or modify the application on the fly without interfering with the source code (for example changing the user interface using skins). More advanced plugins can be found in modern web browsers, such as Firefox or Opera.

More formally, a plugin could be defined as a self-contained piece of executable code that can be dynamically linked into an existing application to modify or extend its functionality. Of course, a plugin is not much use without application code to support it. From a programmer\'s point of view, the key concept here is that of a plugin framework to provide this functionality. The framework should:

  • define an interface for plugins;

  • support one or more implementations of the plugin interface (i.e. actual plugins) that will only be loaded at runtime;

  • provide the client with a plugin management capability, covering such actions as loading, destroying and distinguishing plugins.

Once such a framework is ready, adding and supporting plugins is easy. Now for the good news: in Symbian OS, a plugin framework is integrated into the system, just waiting to be used. Let\'s see how it\'s done.

Creating the IFTool application

To make this ECOM tutorial easier and more engaging, I\'ve decided to prepare a relatively simple yet non-trivial and genuinely useful project to illustrate the stages involved in working with Symbian plugins. The source code is freely available and you are encouraged to experiment with the application and extend its basic functionality.

The program is called IFTool, which is short for Image Filtering Tool. IFTool is a simple graphics manipulation utility that can load an image file (typically from permanent storage), perform basic transformations on the image (i.e. filter it) and write the modified file back to storage. What for? - I hear you ask. Well, if you\'ve ever taken photos using a mobile phone camera you will know they are usually of rather poor quality, especially in low light conditions. However, running such a photo through some elementary filters (even just a basic averaging filter) can significantly improve the quality, so having a simple graphics utility on your phone makes it possible to quickly send the improved photo to your friends by MMS or upload it to your blog.

For the purpose of this article, the most important feature of IFTool is that each filter is implemented as a plugin, so both the author and potential users of the program have the option of easily adding more filters in the future. The basic version presented in the article will offer two ECOM-based filters. Adding other filters is left as an exercise for the reader, as suggested later in the article.

The sample application was developed for the Series 60 platform with Symbian 7.0 or later, but as ECOM is an integral part of the core Symbian system, the same techniques can be used when developing software for other Symbian-based platforms, such as UIQ.

Figure 1 presents sample screenshots of the finished application, while the Frame Quickstart presents the steps necessary to build and run the program.

Figure 1. Screenshots of the IFTool program

Before we get to the actual workshop part, I\'d like to make a quick disclaimer: I am not an expert in image processing, so I realise that my graphics filter implementations are far from perfect. However, I can assure you that the IFTool application itself (especially code dealing with plugins) was designed and developed with utmost care and attention to quality.

Introduction to ECOM

As already mentioned, a plugin support framework consists of three parts: a plugin interface, any number of plugins implementing this interface and a plugin management subsystem. While the first two can relatively easily be implemented in Symbian using a dynamic link library (DLL) with a polymorphic interface (see Frame DLLs in Symbian OS), the third part is much more tricky - implementing a plugin management solution that would be generic, secure and portable across the numerous Symbian-based platforms is very difficult.

DLLs in Symbian OS

A dynamic link library (DLL) is a piece of code that is linked into an application at runtime, unlike static libraries, which are linked at compile time. Symbian OS supports two kinds of DLLs:

  • static interface DLLs,

  • polymorphic interface DLLs.

A DLL with a static interface is automatically loaded into device memory when required (for example when a program that uses it is loaded) and is automatically unloaded when no longer needed. Static interface libraries are typically used to separate application logic from the user interface when implementing application engines.

A DLL with a polymorphic interface has to be explicitly loaded from client code by calling the RLibrary::Load() method and explicitly released using the RLibrary::Close() method. Multiple libraries of this type can implement the same interface, so they are commonly used when working with plugin frameworks.

Client-server architecture in Symbian OS

The client-server architecture integrated into Symbian OS is one of the fundamental design idioms for the platform. The idea itself is nothing new - we have a server providing some services and a client making use of them. However, while the architecture is usually associated with networking, in Symbian OS it provides the basis for process interaction within the system, only the clients and servers are system processes rather than network hosts.

The client-server architecture is one of the factors contributing to the robustness of Symbian OS. All resource access is implemented in this way, meaning that all clients are confined to using specific APIs within their respective processes and therefore cannot (accidentally or otherwise) gain arbitrary access to the system kernel.

Limited versions of this approach are also used in other operating systems. In Unix-based systems, for example, the multilayered structure means that the system kernel in only accessible through the strict API of system calls. However, the designers of Symbian OS went a step further, allowing developers to leverage the system\'s client-server architecture for their own purposes and thus opening up some very interesting possibilities for Symbian application development. You can find more information in the books listed under Further reading.

The creators of Symbian were no doubt aware of this issue and addressed it by developing ECOM - a generic, extensible plugin framework that makes it possible to:

  • identify specific plugin implementations for specific interfaces,

  • select an implementation to use,

  • create an object for the selected implementation and pass it to the plugin client,

  • dispose of the plugin object when it is no longer needed.

Figure 2 presents the architecture and general workings of the ECOM framework, detailing the particular elements of the plugin subsystem and the relations between them. ECOM transparently locates and instantiates the relevant plugin implementation and provides the client with the resulting object, accessible via operations defined for the plugin interface. The framework uses a client-server architecture (see Frame Client-server architecture in Symbian OS), so ECOM-managed plugins can be loaded in parallel by independent client processes. ECOM can also automatically monitor the file system for the appearance of new plugins and features an integrated reference-counting mechanism, allowing unused resources to be automatically deallocated. All this functionality is completely hidden from the programmer. The remainder of the article presents the implementation of ECOM-compliant plugins for the IFTool utility.

Figure 2. ECOM architecture

The ECOM interface

Let\'s start by looking at the process of implementing the ECOM plugin interface.

Listing 1. ECOM plugin interface for the CIFToolPlugIn class

#ifndef __IFTOOL_PLUG_IN_H__
#define __IFTOOL_PLUG_IN_H__
#include <E32Base.h>
#include <ECom.h>
#include <Gdi.h>
// UID for the ECOM interface
const TUid KCIFToolPlugIn_InterfaceUid = { 0x20000AC8 };
class CFbsBitmap;
class CIFToolPlugIn : public CBase
{
public:
// Create default plugin
IMPORT_C static CIFToolPlugIn* NewL();
// Create plugin indicated by the argument
IMPORT_C static CIFToolPlugIn* NewL(
const TDesC8& aCue );
// Destructor
IMPORT_C virtual ~CIFToolPlugIn();
// Build a list of all available implementations
// of this ECOM interface
IMPORT_C static void ListImplementationsL(
RImplInfoPtrArray& aImplInfoArray );
// Filter the source bitmap and draw the result
// to the context in the second argument
IMPORT_C void DoFilterL(
const CFbsBitmap& aSourceBitmap,
CGraphicsContext& aOutputGc );
protected:
IMPORT_C CIFToolPlugIn();
private:
// Filter a single pixel and return its colour
IMPORT_C virtual TRgb FilterPixelL(
const CFbsBitmap& aSourceBitmap,
TPoint aPixelPoint ) = 0;
private:
// Required by ECOM
TUid iDtor_ID_Key;
};
#endif // __IFTOOL_PLUG_IN_H__ 

The code in Listing 1 defines the abstract class corresponding the plugin interface for the IFTool utility. Before we get into working with ECOM itself, a word needs to be said on how plugins are supported within the sample application. For the graphics filter plugin client (i.e. IFTool), the most important method is DoFilterL():

IMPORT_C void DoFilterL(

const CFbsBitmap& aSourceBitmap,

CGraphicsContext& aOutputGc );

The method takes two arguments: the source bitmap corresponding to the image to be filtered and a graphics context instance that the filtered image will be written to. My choice of second argument might at first seem arguable, as specifying a target bitmap instead would appear to be a simpler solution. However, using a graphics context provides more flexibility - CGraphicsContext is an abstract class, so the DoFilterL() method can be passed any object that implements this class. In IFTool the context will draw to a bitmap in memory, but another program using the same interface might for example draw directly to the screen. When defining a plugin interface, it\'s well worth sticking to the simple rule of not limiting the client, which can be achieved by defining operations as generically as possible (within the bounds of reason, of course). If you ignore this rule, it will quickly come back to haunt you in practice.

Listing 2. Method definitions for the CIFToolPlugIn class

#include "IFToolPlugIn.h"
#include <Fbs.h>
EXPORT_C CIFToolPlugIn::CIFToolPlugIn()
{
}
EXPORT_C CIFToolPlugIn::~CIFToolPlugIn()
{
// Releases the resources allocated for this object
// and notifies ECOM that the plugin instance has been
// destroyed
REComSession::DestroyedImplementation( iDtor_ID_Key );
}
EXPORT_C CIFToolPlugIn* CIFToolPlugIn::NewL()
{
// Default plugin - an averaging filter
const TUid KCIFToolPlugIn_AveragingFilterUid
=
{ 0x20000ACA };
TAny* defaultIFToolPlugin =
REComSession::CreateImplementationL(
KCIFToolPlugIn_AveragingFilterUid,
_FOFF( CIFToolPlugIn, iDtor_ID_Key ) );
return reinterpret_cast< CIFToolPlugIn*>(
defaultIFToolPlugin );
}
EXPORT_C CIFToolPlugIn* CIFToolPlugIn::NewL( const TDesC8& aCue )
{
// Use the default ECOM resolver
TEComResolverParams resolverParams;
resolverParams.SetDataType( aCue );
// Allow wildcards in the hint string
resolverParams.SetWildcardMatch( ETrue );
TAny* ifToolPlugIn =
REComSession::CreateImplementationL(
KCIFToolPlugIn_InterfaceUid,
_FOFF( CIFToolPlugIn, iDtor_ID_Key ),
NULL, resolverParams );
return reinterpret_cast< CIFToolPlugIn*>(
ifToolPlugIn );
}
EXPORT_C void CIFToolPlugIn::ListImplementationsL(
RImplInfoPtrArray& aImplInfoArray )
{
// Fetch a list of available implementations
REComSession::ListImplementationsL(
KCIFToolPlugIn_InterfaceUid,
aImplInfoArray );
}
EXPORT_C void CIFToolPlugIn::DoFilterL(
const CFbsBitmap& aSourceBitmap,
CGraphicsContext& aOutputGc )
{
TInt sourceBitmapWidth
=
aSourceBitmap.SizeInPixels().iWidth;
TInt sourceBitmapHeight
=
aSourceBitmap.SizeInPixels().iHeight;
TPoint pixel;
aOutputGc.SetPenSize( TSize( 1, 1 ) );
for ( pixel.iX = 1; pixel.iX < sourceBitmapWidth;
++
pixel.iX )
{
for ( pixel.iY = 1; pixel.iY < sourceBitmapHeight;
++
pixel.iY )
{
aOutputGc.SetPenColor(
FilterPixelL( aSourceBitmap, pixel ) );
aOutputGc.Plot( pixel );
}
}
}
// DLL entry point
GLDEF_C TInt E32Dll( TDllReason /*aReason*/ )
{
return KErrNone;
}

In the light of the method description presented above, you may be wondering what the IMPORT_C modifier is for and why DoFilterL() is not declared as virtual. Allow me to explain. IMPORT_C means that the method will be exported out of a DLL, i.e. it will be available for clients to call as part of the DLL\'s interface. Explaining the non-virtual nature of the method requires a brief digression. The idea is that the plugin method expected by the client need not necessarily be the actual same method that provides unique plugin functionality. For our sample application, we are assuming that filtering will take place at pixel level, so defining DoFilterL() as a pure virtual method would require each plugin implementation to supply its own code for reading subsequent pixels, thus quite clearly duplicating functionality between implementations. Note that for our filter plugins we\'re also assuming that operations on subsequent pixels do not modify the source bitmap, so the order in which pixels are processed is irrelevant to the resulting image. This makes it possible to define the private and pure virtual FilterPixelL() method which is uniquely implemented for each plugin and provides the actual filtering operation for particular pixels. If you take a look at the implementation of DoFilterL() in Listing 2, you can see FilterPixelL() being called for each source pixel in turn. This approach to designing base classes is quite common - it is in fact the Template method design pattern.

Getting back to the main story, a class implementing the ECOM interface has to:

  • be abstract, i.e. contain at least one pure virtual method;

  • provide at least one factory class, allowing clients to fetch a specified implementation;

  • provide a means of releasing allocated resources, for example through a destructor or a dedicated clean-up method (e.g. Close());

  • contain a member of type TUid, required for implementing automatic resource release.

Let\'s go over these requirements, comparing them to the source code in Listing 2. The abstractness requirement is pretty obvious - without it there would be little point in creating a plugin in the first place. The factory method implementations are in line with the two-phase construction model in Symbian OS. Our sample code uses two versions of the NewL() method. The first version takes no arguments and handles the creation of a default plugin - an unofficial requirement when working with ECOM. The key operation within the method is the call to REComSession::CreateImplementationL(), which is passed the plugin identifier (UID) and the address of a TUid member fetched using the _FOFF macro. The call returns the ECOM pointer to the default plugin implementation (in this case an averaging filter). The returned pointer type is TAny* (the Symbian equivalent of void*), so before the plugin is passed to the client it needs to be cast to the same type as the plugin interface (in our case CIFToolPlugIn). For this application, I\'ve used the reinterpret_cast operator to do the actual cast.

Listing 3. IFToolPlugIn component definition

TARGET       IFToolPlugIn.dll
TARGETTYPE dll
UID 0x1000008D 0x20000AC7
USERINCLUDE ..inc
SYSTEMINCLUDE epoc32include
SYSTEMINCLUDE epoc32includeecom
SOURCEPATH ..src
SOURCE IFToolPlugIn.cpp
LIBRARY ECom.lib
LIBRARY EUser.lib
LIBRARY FbsCli.lib
LIBRARY Gdi.lib

The overloaded version of the factory method takes one argument: a descriptor (the Symbian OS equivalent of a string) providing a hint concerning plugin selection. Looking at the method body, you can see the construction of a TEComResolverParams object. Objects of this class identify particular plugins, making it possible for ECOM to select the right implementation. The remainder of the method body is similar to that of the default version, except that REComSession::CreateImplementationL() is called with two extra arguments. The first parameter can be used whenever the construction of a specific plugin requires external data to be passed - since we have no need of that, we can pass NULL. The other parameter is the TEComResolverParams object discussed above, providing a hint concerning plugin selection.

The next method to examine is ListImplementationsL(), which calls REComSession::ListImplementationsL() to fetch ECOM\'s list of available plugin implementations for the interface with the specified UID and writes that list to the address passed in the argument. You will notice that implementing ECOM plugins involves using a variety of UIDs for a variety of purposes, which can at first be a bit confusing. See the Frame ECOM and UIDs for a description of all the identifiers used in the examples - reading it once you\'re familiar with the article will help you get a better overall picture of the whole scheme.

Listing 4. IFToolPlugInsDefault component definition

TARGET       IFToolPlugInsDefault.dll
TARGETTYPE ECOMIIC
UID 0x10009D8D 0x20000AC9
SOURCEPATH ..src
SOURCE IFToolPlugIn_AveragingFilter.cpp
SOURCE IFToolPlugIn_MedianFilter.cpp
SOURCE IFToolPlugInsDefault_Main.cpp
SOURCE IFToolPlugInsDefault_Proxy.cpp
SOURCEPATH ..data
RESOURCE 20000AC9.rss
USERINCLUDE ..inc
SYSTEMINCLUDE epoc32include
SYSTEMINCLUDE epoc32includeecom
LIBRARY EUser.lib
LIBRARY FbsCli.lib
LIBRARY Gdi.lib
LIBRARY IFToolPlugIn.lib

To fulfil the requirements of the ECOM interface, we also need a destructor. Looking at Listing 2, you can see that destroying a plugin involves calling REComSession::DestroyedImplementation() with the magic argument iDtor_ID_Key.

To complete this discussion of the ECOM plugin interface, have a look at the definition file for the IFPlugIn component, shown in Listing 3. The ECOM interface is physically implemented as a DLL with a static interface, as indicated by the value 0x1000008D in the UID line. The component is also linked to the ECOM library in the line:

LIBRARY ECom.lib

Implementing the ECOM interface

With the ECOM interface ready, we can set about implementing some actual plugins. ECOM allows multiple plugins to be defined within one DLL, so all the plugins for our sample application will be implemented within the IFPlugInsDefault component. Listing 4 presents the contents of the component definition file. Here are the four most important entries:

  • TARGETTYPE ECOMIIC: the component is a DLL containing an ECOM interface implementation;

  • UID 0x10009D8D 0x10009EE1: the first value is a constant library type, while the second is the current component\'s unique UID;

  • RESOURCE 20000AC9.rss: a link to the current implementation\'s resource file (discussed below);

  • LIBRARY ECom.lib: information for the linker that the component requires the ECOM library to be linked.

Now we know the component definition, let\'s have a look at the source code implementing our two basic plugins (Listings 5 and 6). The component is implemented as a DLL, so we also need to define a suitable entry point:

TBool E32Dll()

{

return (ETrue);

}

The definition resides in the source file IFToolPlugInsDefault_Main.cpp.

As already mentioned, one ECOM interface implementation can contain several actual plugin definitions. For the purpose of our example, the IFPlugIns component implements two plugins: an averaging filter and a median filter. Although both filters are very simple, discussing the details of their operation is beyond the scope of this article, so for more information on them you can consult external resources or simply study the IFTool source. Listing 5 contains the class definition for the averaging filter plugin - the class extends CIFToolPlugIn, but apart from that it\'s pretty straightforward.

Listing 5. ECOM plugin implementation - CIFToolPlugin_AveragingFilter class definition

#ifndef __IFTOOL_PLUG_IN__AVERAGING_FILTER_H__
#define __IFTOOL_PLUG_IN__AVERAGING_FILTER_H__
#include <IFTool/IFToolPlugIn.h>
class CIFToolPlugin_AveragingFilter
: public CIFToolPlugIn
{
public:
static CIFToolPlugin_AveragingFilter* NewL();
~CIFToolPlugin_AveragingFilter();
private: // Plugin interface implementation
TRgb FilterPixelL( const CFbsBitmap& aSourceBitmap,
TPoint aPixelPoint );
private:
CIFToolPlugin_AveragingFilter();
void ConstructL();
void InitializeMask();
private:
TInt iMask


Web Design Services